//world.h

#ifndef WORLD_H_INCLUDED
#define World_H_INCLUDED
#include <vector>
#include <freeglut.h> 
#include "gamedrawer.h"
#include <Box2d/Box2D.h>
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class Container;
class WallSection;
class Item;
class Definition;
class Reactor; 
class Blueprint;
class Request;
class Bot;
class Job;
class Observer;
class ItemStack;
class Reservation;
class World{
public:
	World();
	void update();
	void draw();
	void select();
	void deselect_all();
	void drag();
	void grab(int x, int y);
	void release();
	void addItem(Item * itm);
	void removeItem(Item * itm);
	void order_duplicate();
	void order_convert();
	void order_toggle_active_state();
	void temp_Test();
	void createRequest(Container *, string, ItemStack);
	void registerStack(ItemStack*);
	void unregisterStack(ItemStack*);
	bool get(string s, vector<Item*>*, int count);
	bool getAt(string s, vector<Item*>*, int count, Coord, Coord);
	void addIdleBot(Bot *);
	void toggle_designate();
	b2World * worldSim;
	b2Body * createBody(Item * itemIn, b2Vec2 pointIn, b2Shape * shapeIn);
	vector<string> getDef(string defName);
private:
	vector<ItemStack*> inventories;
	vector<Item *> designatedItems;
	vector<Item *> itemList;
	vector<Item *> hovered;
	vector<Item *> selected;
	vector<Definition *> definitions;
	Item * grabbed;
	vector<Request *> requests;
	//vector<Request *> offers;
	vector<Job> jobs;
	vector<Observer *> observers;
	vector<Bot *> idleBots;
	void createJobs();
	void matchJobToBot();
	bool designate;
	bool designate_started;
	Coord designate_start;
	Coord designate_end;
	void do_designate();
	void createDeliveryJobs(unsigned int amountToFind, unsigned int currentRequest);
	void createCollectJobs(unsigned int amountToFind, unsigned int currentRequest);
};

class Item{
	public:
	Item(){
		owner = NULL;
	}
	~Item(){
		world->worldSim->DestroyBody(body);
	};
	Container * owner;
	b2CircleShape shape;
	b2Body * body;
	static World * world;
	static int next_ID;
	string name;
	float drawRadius;
	GLuint textureID;
	void setID();
	void generateShape(string defName);
	vector<string> tags;
	bool hasTag(string tag);
	virtual void update()=0;
	virtual void draw()=0;
	virtual void command(string)=0;
	void defaultDraw();
};

class GenericItem: public Item{
	public:
		GenericItem(string defName, b2Vec2 pointIn){
			generateShape(defName);
			body = world->createBody(this, pointIn, &shape);
		};
		void update();
		void draw();
		void command(string);
		void generateFromDefinition(string defName, b2CircleShape * myShape);
};

class Definition{
	public:
	Definition(vector<string> linesIn){
		name = "no_name";
		lines = linesIn;
		string str;
		for(unsigned int i = 0; i < lines.size(); i++){
			str = lines[i];
			if(str.compare(0,5,"name:")==0){
				name = str.substr(5,str.size());
			}
			if(str.compare("[def]")==0||str.compare("[/def]")==0){
				lines.erase(lines.begin()+i,lines.begin()+i+1);
			}
		}
	}
	string name;
	vector<string> lines;
	static vector<Definition *> parseFile(string);
};

class Blueprint: public Item{
	public:
	Blueprint::Blueprint(string defName, b2Vec2 pointIn){
		generateShape("blueprint");
		body = world->createBody(this, pointIn, &shape);
		duplication_cycle_timer = 0;
		conversion_cycle_timer = 0;
		generatefromDefinition(defName);
	};
	//inputs, outputs, reaction time, size range, power consumption, 
	//inputs to convert to Reactor, inputs to duplicate, current-size setting.
	private:
	void generatefromDefinition(string defName);

	public:
	vector<ItemStack> reaction_inputs;
	vector<ItemStack> reaction_outputs;
	vector<ItemStack> conversion_inputs;
	vector<Item *> held_inputs;
	vector<ItemStack> duplication_inputs;

	int reaction_time;
	int duplication_time;
	int conversion_time;
	b2Vec2 reaction_size_range;
	b2Vec2 conversion_size_range;
	float currentSize;
	float reaction_power_consumption;
	float conversion_power_consumption;
	float duplication_power_consumption;
	void command(string);
	void update();
	void draw();
	int duplication_cycle_timer;
	int conversion_cycle_timer;
	
	void duplicate();
	void convert();

	bool duplicate_hasInputs();
	bool conversion_hasInputs();
	void setupInputs();
};

class Observer{
public:
	virtual void notify()=0;
};
class ObserverSubject{
public:
	void registerObserver(Observer *);
	void unregisterObserver(Observer *);
	void notifyObservers();
	vector<Observer *> observers;
};

class Container: public Item, public ObserverSubject{
	public:
		Container(b2Vec2 pointIn){
			generateShape("container");
			body = world->createBody(this, pointIn, &shape);
		}
		void command(string);
		//void update();
		//void draw();
		void addItem(Item *);//adds an item to the container
		void erase(Item * itm);//removes an item from the container
		void update();
		void draw();
		unsigned int reserve(string type, unsigned int count);
		void calculateSize();//figures out the size of the container by counting walls
		void arrangeWalls();//gives walls a place around the edge of the container
		bool find(string s, vector<Item*>*, unsigned int count);
		ItemStack * find(string s);
		Item * take(Bot *, string);//returns the requested item	
		Item * getItemAt(float x, float y);//returns an (the) item at the input location
		Item * getItemAt(b2Vec2 pointIn);
		vector<ItemStack *> pending_requests;
	private:
		void attach(Item * item);
		void detach(Item * item);
		vector<ItemStack*> inventory;
		vector<ItemStack*> reserved_inventory;
		vector<Item *> wall_sections;
		vector<Item *> internal_items;
		void changeInventory(string itemName, int);
		void changeReservedInventory(string, int);
};
class Reactor: public Item, public Observer{
	public:
	Reactor(Blueprint * blueprintIn, float sizeIn){
		generateShape("reactor");
		owner = blueprintIn->owner;
		owner->registerObserver(this);		
		body = world->createBody(this, blueprintIn->body->GetPosition(), &shape);
		blueprint = blueprintIn->name;
		reaction_cycle_timer = 0;
		conversion_cycle_timer = 0;
		reaction_time = blueprintIn->reaction_time;
		conversion_time = blueprintIn->conversion_time*4;

		conversion_inputs = blueprintIn->conversion_inputs;
		reaction_inputs = blueprintIn->reaction_inputs;
		reaction_outputs = blueprintIn->reaction_outputs;
		world->removeItem(blueprintIn);
		active = true;
		inputs_changed = true;
	};
	bool active;
	bool inputs_changed;
	string blueprint;
	vector<ItemStack> reaction_inputs;
	vector<ItemStack> reaction_outputs;
	vector<ItemStack> conversion_inputs;
	vector<Item *> held_inputs;
	int reaction_time;
	float reaction_power_consumption;
	int conversion_time;
	float conversion_power_consumption;

	void react();
	void convert();
	void update();
	void draw();
	void command(string);
	void notify();
	int reaction_cycle_timer;
	int conversion_cycle_timer;
	//bool reaction_hasInputs();
	//bool conversion_hasInputs();
	bool canClaimInputs();
	vector<ItemStack> reactionInputsNeeded();
	vector<ItemStack> conversionInputsNeeded();
private:
	vector<ItemStack> pending_requests;
	void addPendingRequest(ItemStack);
	void requestInputs();
	void removeInputs();
};

class Storage: public Item{
public:
	Storage(b2Vec2 pointIn){
		type = "none";
		generateShape("storage");
		body = world->createBody(this, pointIn, &shape);
	};
	Storage(string,b2Vec2);
	void update();
	void draw();
	void command(string);
private:
	string type;
	vector<Item *> inventory;
};

class ItemStack{
public:
	ItemStack(string typeIn, int countIn){
		type= typeIn;
		count = countIn;
		owner = NULL;
	}
	ItemStack(){
		type = "no_type";
		count = 0;
		owner = NULL;
	}
	Container * owner;
	string type;
	unsigned int count;
};

class Request{
public:
	Request(Container * caller, string message, ItemStack itemstack){
		requester = caller;
		requestType = message;
		itemStack = itemstack;
	}
	Container * requester;
	string requestType;
	ItemStack itemStack;
};

class Job{
public:
	string cargo;
	Item * source;
	Container * destination;
	string jobType;
};

class Mover{
public:
	b2Vec2 target;
	float maxAngularVelocity;
	float maxLinearVelocity;
	float maxLinearAccel;
	bool moveTowardTarget();
	bool turnTowardTarget();
	bool stop();
	b2Body * moverbody;
};

class Bot: public Item, public Observer, public Mover{
public:
	Bot(b2Vec2 pointIn){
			generateShape("cargo bot");
			body = world->createBody(this, pointIn, &shape);
			moverbody = body;
			owner = NULL;
			cargo = NULL;
			state = "idle";
			maxLinearVelocity = 60;
			maxLinearAccel = 3;	
			target = body->GetPosition();
			//addIEF();
			world->addIdleBot(this);
	}
	void notify();
	void update();
	void draw();
	void command(string);
	void setJob(Job);
	void attach(Item* item);
	void detach(Item* item);
	Job job;
	Item * cargo;
	string state;
private:
	//string state;
	void doPickup();
	void doDeliver();
	void doCollect();
	void dropOff();
};

#endif